#include "opengles.h"

#define MAX_PIPE_NUM 128
static char gles_SystemUuidPrefix[128];
static RTCRITSECT read_lock;
static RTCRITSECT opengles_cs;
static struct channel_pipe *PipePool = NULL;

static void PipePoolinit()
{
	RTCritSectInit(&opengles_cs);
	PipePool = new channel_pipe[PIPE_BUFFER_LEN];
	for (int i = 0; i < MAX_PIPE_NUM; i++)
	{
		PipePool[i].pipe = INVALID_HANDLE_VALUE;
		PipePool[i].channel = -1;
		PipePool[i].readbuffer = NULL;
	}
}

static struct channel_pipe *GetPipeStruct(int64_t channel)
{
	RTCritSectEnter(&opengles_cs);
	for (int i = 0; i < MAX_PIPE_NUM; i++)
	{
		if (PipePool[i].channel == channel)
		{
			RTCritSectLeave(&opengles_cs);
			return &PipePool[i];
		}
	}
	RTCritSectLeave(&opengles_cs);
	return nullptr;
}

static void CloseReadThread(struct channel_pipe *conn)
{
	int rc;
	rc = RTSemEventSignal(conn->ChannelRead);
	AssertRC(rc);
	rc = RTThreadWait(conn->ReadThread, RT_INDEFINITE_WAIT, NULL);
	AssertRC(rc);
	if (conn->ol_read.hEvent != NULL)
	{
		CloseHandle(conn->ol_read.hEvent);
		conn->ol_read.hEvent = NULL;
	}
	if (conn->ol_write.hEvent != NULL)
	{
		CloseHandle(conn->ol_write.hEvent);
		conn->ol_write.hEvent = NULL;
	}
	rc = RTSemEventDestroy(conn->ChannelRead);
	AssertRC(rc);
}

static void removePipe(int64_t channel)
{

	if (channel < 0)
		return;
	RTCritSectEnter(&opengles_cs);
	for (int i = 0; i < MAX_PIPE_NUM; i++)
	{
		if (PipePool[i].channel == channel)
		{
			PipePool[i].channel = -1;
			if (PipePool[i].pipe != INVALID_HANDLE_VALUE)
			{
				FlushFileBuffers(PipePool[i].pipe);
				DisconnectNamedPipe(PipePool[i].pipe);
				CloseHandle(PipePool[i].pipe);
				PipePool[i].pipe = INVALID_HANDLE_VALUE;
			}
			CloseReadThread(&PipePool[i]);
			if (PipePool[i].elem != NULL)
			{
				delete PipePool[i].elem;
				PipePool[i].elem = NULL;
			}
			if (PipePool[i].readbuffer != NULL)
			{
				delete[] PipePool[i].readbuffer;
				PipePool[i].readbuffer = NULL;
			}
			break;
		}
	}
	RTCritSectLeave(&opengles_cs);
}

static bool addPipeHandle(int64_t channel)
{
	if (channel < 0)
		return false;
	RTCritSectEnter(&opengles_cs);
	for (int i = 0; i < MAX_PIPE_NUM; i++)
	{
		if (PipePool[i].channel == -1)
		{
			PipePool[i].pipe = pipeConnect(PIPE_NAME_FORMAT, gles_SystemUuidPrefix);
			TRACE("addPipeHandle  get channel:%lld PipePool.pool[i].pipe (%d)", channel, PipePool[i].pipe);
			if (PipePool[i].pipe != INVALID_HANDLE_VALUE)
			{
				TRACE("addPipeHandle channel:%lld ~ hpipe(%d)", channel, PipePool[i].pipe);
				PipePool[i].channel = channel;
				memset(&PipePool[i].ol_read, 0, sizeof(PipePool[i].ol_read));
				PipePool[i].ol_read.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
				memset(&PipePool[i].ol_write, 0, sizeof(PipePool[i].ol_write));
				PipePool[i].ol_write.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
				PipePool[i].readbuffer = new unsigned char[PIPE_BUFFER_LEN];
				RTSemEventCreate(&PipePool[i].ChannelRead);
				RTThreadCreate(&PipePool[i].ReadThread, PipeReadThread, (void *)&PipePool[i], 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "PipeRead");
				RTCritSectLeave(&opengles_cs);
				return true;
			}
		}
	}
	RTCritSectLeave(&opengles_cs);
	return false;
}

/* -=-=-=-=- PCI Device -=-=-=-=- */

static DECLCALLBACK(int) glesMap(PPCIDEVICE pPciDev, int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
{
	OpenglesState *pThis = PDMINS_2_DATA(pPciDev->pDevIns, OpenglesState *);
	int rc;

	if (enmType != PCI_ADDRESS_SPACE_IO)
	{
		return VERR_INTERNAL_ERROR;
	}

	pThis->VPCI.IOPortBase = (RTIOPORT)GCPhysAddress;
	rc = PDMDevHlpIOPortRegister(pPciDev->pDevIns, pThis->VPCI.IOPortBase,
								 cb, 0, ptIOPortOut, ptIOPortIn,
								 NULL, NULL, "VirtIO_GLES");
	AssertRC(rc);
	return rc;
}

//static DWORD WINAPI PipeReadThread(LPVOID pvPipe) //rx
static DECLCALLBACK(int) PipeReadThread(RTTHREAD ThreadSelf, void *pvUser)
{

	struct channel_pipe *conn = (struct channel_pipe *)pvUser;
	int rc = 0;
	unsigned int len = 0;

	while (conn->pipe != INVALID_HANDLE_VALUE)
	{
		memset(conn->readbuffer, 0, PIPE_BUFFER_LEN);
		len = 0;
		rc = pipeRead(conn->pipe, conn->readbuffer, &len, &conn->ol_read);
		TRACE("gles_GuestThreadRead: channel:%lld done_size(%d) rc(%d)", conn->channel, len, rc);
		if (rc == VINF_SUCCESS)
		{
			do
			{
				RTSemEventWait(conn->ChannelRead, RT_INDEFINITE_WAIT);
				if (conn->elem)
					break;
			}
			while (!conn->elem);

			RTCritSectEnter(&read_lock);
			conn->elem->aSegsIn[0].pv = conn->readbuffer; //>aSegsIn[0].pv != 0 vqueuePut will write 
			vqueuePut(&conn->dev->VPCI, conn->dev->pRxQueue, conn->elem, len);
			vqueueSync(&conn->dev->VPCI, conn->dev->pRxQueue);
			if (conn->elem)
			{
				delete conn->elem;
				conn->elem = NULL;
			}
			RTCritSectLeave(&read_lock);
		}
		else
		{
			break;
		}
	}
	//free
	return 0;
}

//win to android (e.g. android call read)
void gles_GuestRead(void *pvState, PVQUEUE pQueue) //rx
{
	OpenglesState *pThis = (OpenglesState *)pvState;
	int64_t channel;
	VQUEUEELEM elem;
	struct channel_pipe *conn;
	int rc;

	while (vqueueGet(&pThis->VPCI, pThis->pRxQueue, &elem))
	{
		// kernel4.0 side
		// sg_init_one(&header, (void *)&conn_pipe->channel, sizeof(conn_pipe->channel));
		// sgs[0] = &header;
		// sg_init_one(&in, buffer->buf, buffer->size);
		// sgs[1] = &in;
		// virtqueue_add_sgs(glesdevice->vqs[VQ_RX], sgs, 1, 1, buffer, GFP_KERNEL)

		PDMDevHlpPhysRead(pThis->VPCI.CTX_SUFF(pDevIns), elem.aSegsOut[0].addr, &channel, sizeof(channel));
		conn = GetPipeStruct(channel);
		if (conn && conn->pipe != INVALID_HANDLE_VALUE)
		{
			VQUEUEELEM *elemclone = new VQUEUEELEM;
			memcpy(elemclone, &elem, sizeof(VQUEUEELEM));
			conn->elem = elemclone;
			conn->dev = pThis;
			rc = RTSemEventSignal(conn->ChannelRead);
			AssertRC(rc);
		}
		else
		{
			TRACE("gles_GuestRead: channel:%lld pipe not found", channel);
			RTCritSectEnter(&read_lock);
			vqueuePut(&pThis->VPCI, pThis->pRxQueue, &elem, 0);
			vqueueSync(&pThis->VPCI, pThis->pRxQueue);
			RTCritSectLeave(&read_lock);
		}
	}
	return;
}

static DECLCALLBACK(int) PipeWriteThread(RTTHREAD ThreadSelf, void *pvUser) //tx
{
	OpenglesState *pThis = (OpenglesState *)pvUser;
	VQUEUEELEM elem;
	int64_t channel;
	int rc = 0;
	unsigned int done_size = 0;
	struct channel_pipe *conn;
	unsigned char *writebuffer = new unsigned char[0x400000]; //0x400000?
	unsigned int offset = 0;
	unsigned int len;
	while (pThis->WriteLoop)
	{
		RTSemEventWait(pThis->EventWrite, RT_INDEFINITE_WAIT);
		while (vqueueGet(&pThis->VPCI, pThis->pTxQueue, &elem))
		{
			done_size = 0;
			PDMDevHlpPhysRead(pThis->VPCI.CTX_SUFF(pDevIns), elem.aSegsOut[0].addr, &channel, sizeof(channel));
			conn = GetPipeStruct(channel);
			if (conn && conn->pipe != INVALID_HANDLE_VALUE)
			{
				memset(writebuffer, 0, 0x400000);
				//TRACE("gles_GuestWrite: elem.aSegsOut[1] addr %p cb %d", elem.aSegsOut[1].addr, elem.aSegsOut[1].cb);
				//TRACE("gles_GuestWrite: cb %d", elem.aSegsOut[1].cb);
				done_size = elem.aSegsOut[1].cb;
				PDMDevHlpPhysRead(pThis->VPCI.CTX_SUFF(pDevIns), elem.aSegsOut[1].addr, writebuffer, elem.aSegsOut[1].cb);
				vqueuePut(&pThis->VPCI, pThis->pTxQueue, &elem, 0);
				vqueueSync(&pThis->VPCI, pThis->pTxQueue);

				if (done_size)
				{
					offset = 0;
					do
					{
						len = 0x400000;
						if (done_size - offset < 0x400000)
							len = done_size - offset;
						rc = pipeWrite(conn->pipe, writebuffer, &len, &conn->ol_write);
						TRACE("gles_GuestWrite: channel:%lld Opcode(%d) cb(%d) done_size(%d) rc(%d)",
							  conn->channel, int(writebuffer[1] * 256 + writebuffer[0]), elem.aSegsOut[1].cb, done_size, rc);
						if (rc != VINF_SUCCESS)
						{
							removePipe(channel);
							break;
							//continue;
						}
						offset += len;
					} while (offset < done_size);
				}
			}
			else
			{
				TRACE("gles_GuestWrite: channel:%lld INVALID_HANDLE_VALUE", channel);
				vqueuePut(&pThis->VPCI, pThis->pTxQueue, &elem, 0);
				vqueueSync(&pThis->VPCI, pThis->pTxQueue);
				//continue;
			}
		}
	}

	delete[] writebuffer;
	return 0;
}

//android to win (e.g. android call write)
static void gles_GuestWrite(void *pvState, PVQUEUE pQueue)
{
	int rc;
	OpenglesState *pThis = (OpenglesState *)pvState;
	rc = RTSemEventSignal(pThis->EventWrite);
	AssertRC(rc);
	return;
}

static void gles_QueueCtrl(void *pvState, PVQUEUE pQueue)
{
	int64_t channel;
	int opcode;
	int status = 1;
	struct channel_pipe *conn;

	// KERNEL side
	// sg_init_one(&header, (void *)&conn_pipe->channel, sizeof(conn_pipe->channel));
	// sgs[0] = &header;
	// sg_init_one(&out, (void *)&opcode, sizeof(int));
	// sgs[1] = &out;
	// sg_init_one(&in, (void *)status), sizeof(status));
	// sgs[2] = &in;

	OpenglesState *pThis = (OpenglesState *)pvState;
	VQUEUEELEM elem;

	while (vqueueGet(&pThis->VPCI, pQueue, &elem))
	{
		//request channel
		PDMDevHlpPhysRead(pThis->VPCI.CTX_SUFF(pDevIns), elem.aSegsOut[0].addr, &channel, sizeof(channel));
		PDMDevHlpPhysRead(pThis->VPCI.CTX_SUFF(pDevIns), elem.aSegsOut[1].addr, &opcode, sizeof(opcode));
		if (VIRTIO_RELEASE == opcode)
		{
			TRACE("gles_QueueCtrl channel:<<<<----cloes---------------%lld, opcode(%d)", channel, opcode);
			status = -1;
			conn = GetPipeStruct(channel);
			if (conn)
			{
				//pipeClose(conn->pipe);
				removePipe(channel);
			}
			elem.aSegsIn[0].pv = &status;
		}
		else if (VIRTIO_OPEN == opcode)
		{
			status = addPipeHandle(channel) ? 1 : -1;
			conn = GetPipeStruct(channel);
			TRACE("gles_QueueCtrl channel:------start------------->>>>%lld, opcode(%d)", channel, opcode);
			elem.aSegsIn[0].pv = &status;
		}
		vqueuePut(&pThis->VPCI, pQueue, &elem, sizeof(status));
		vqueueSync(&pThis->VPCI, pQueue);
	}
}

static int glesConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
{
	OpenglesState *pThis = PDMINS_2_DATA(pDevIns, OpenglesState *); //c_of
	int rc;

	TRACE("enter: glesConstruct");
	RTUUID uuid;
	rc = CFGMR3QueryBytes(pCfg, "UUID", &uuid, sizeof(uuid));
	if (RT_FAILURE(rc))
		return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Querying \"UUID\" failed"));

	RTUuidToStr(&uuid, gles_SystemUuidPrefix, sizeof(gles_SystemUuidPrefix));
	PipePoolinit();
	rc = vpciConstruct(pDevIns, &pThis->VPCI, iInstance, GLES_NAME_FMT, GLES_PCI_SUBSYSTEM_ID, GLES_PCI_CLASS, GLES_N_QUEUES);
	if (RT_FAILURE(rc))
		return rc;
	rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, VPCI_CONFIG, PCI_ADDRESS_SPACE_IO, glesMap);
	if (RT_FAILURE(rc))
		return rc;
	pThis->pTxQueue = vpciAddQueue(&pThis->VPCI, 256, (PFNVPCIQUEUECALLBACK)gles_GuestWrite, "TX");
	pThis->pRxQueue = vpciAddQueue(&pThis->VPCI, 256, (PFNVPCIQUEUECALLBACK)gles_GuestRead, "RX");
	pThis->pCtlQueue = vpciAddQueue(&pThis->VPCI, 256, (PFNVPCIQUEUECALLBACK)gles_QueueCtrl, "CTRL");

	RTCritSectInit(&read_lock);
	rc = RTSemEventCreate(&pThis->EventWrite);
	AssertRC(rc);
	pThis->WriteLoop = 1;
	rc = RTThreadCreate(&pThis->WriteThread, PipeWriteThread, (void *)pThis, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "PipeWrite");
	return rc;
}

static int glesDestruct(PPDMDEVINS pDevIns)
{
	OpenglesState *pThis = PDMINS_2_DATA(pDevIns, OpenglesState *);
	int rc;
	pThis->WriteLoop = 0;
	rc = RTSemEventSignal(pThis->EventWrite);
	AssertRC(rc);
	rc = RTThreadWait(pThis->WriteThread, RT_INDEFINITE_WAIT, NULL);
	AssertRC(rc);
	rc = RTSemEventDestroy(pThis->EventWrite);
	AssertRC(rc);
	rc = RTCritSectDelete(&read_lock);
	AssertRC(rc);
	rc = RTCritSectDelete(&opengles_cs);
	AssertRC(rc);
	return rc;
}

/**
 * The device registration structure.
 */
const PDMDEVREG g_DeviceGLES =
	{
		/* Structure version. PDM_DEVREG_VERSION defines the current version. */
		PDM_DEVREG_VERSION,
		/* Device name. */
		"VirtIO_GLES",
		"",
		"",
		"VirtIO_GLES Device.\n",
		PDM_DEVREG_FLAGS_DEFAULT_BITS,
		PDM_DEVREG_CLASS_MISC,
		/* Maximum number of instances (per VM). */
		~0U,
		/* Size of the instance data. */
		sizeof(OpenglesState),
		glesConstruct,
		glesDestruct,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		PDM_DEVREG_VERSION};